<!doctype html>
<html>

<head>
	<style>
		li {
			width: 300px;
			height: 100px;
			margin-bottom: 8px;
			&.item1{
				background-color: rgb(0, 26, 255);
			}
			&.item2{
				background-color: rgb(255, 0, 212);
			}
			&.item3{
				background-color: rgb(202, 235, 17);
			}
		}

		.container {
			border: 5px solid green;
			width: 300px;
			height: 200px;
		}

		.mark {
			position: relative;
		}

		.mark ::after {
			content: '';
			position: absolute;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background-color: rgba(255, 255, 0, 0.2);
		}
	</style>
	<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
	<ol class="drag-list">
		<li v-for="i in 3" draggable="true" @dragstart="ondragstart" @dragenter="ondragenter"
			@dragover="ondragover" @dragend="ondragend" @drop="ondrop" :class="[{mark:dragingNode}, 'item'+i]">
		</li>
	</ol>
	<div class="container">
		container ready
	</div>
	<script>
		const { ref, createApp, onMounted } = Vue;
		createApp({
			setup() {
				let flipDh = null;
				onMounted(() => {
					const dragList = document.querySelector('.drag-list');
					flipDh = new FlipAnimation(dragList.children);
				});
				const dragingNode = ref(null);
				const ondragstart = (e) => {
					//获取当前拖拽元素
					console.log('拖拽开始');
					dragingNode.value = e.currentTarget;

					//设置自定义拖拽图像
					// var img = new Image ();
					// img.src = 'url';
					// e.dataTransfer.setDragImage ( img , 10 , 10 );

					//DataTransfer 包含两个信息，数据的类型和数据值。类型是一个类型字符串（例如文本数据的格式是text/plain），值是一个文本字符串。
					//拖拽开始时提供数据类型和数据值。 dragenter 和 dragover 事件可以获取类型,drop 事件都可获取
					e.dataTransfer.setData('text/plain', 'This text may be dragged');
					//自定义类型,其他应用程序不会支持这个类型，
					e.dataTransfer.setData('custom', 'This is custom stats');

					//effectAllowed必须兼容目标元素ondragover事件中的dropEffect属性才会触发目标元素的drop事件且鼠标不为禁止状态
					//none|copy|move|link|copyMove|copyLink|linkMove|all
					e.dataTransfer.effectAllowed = 'copyMove';
				};
				const ondragenter = (e) => {
					//判断是否为允许进入的元素
					const dropNode = e.currentTarget;
					if (dropNode === dragingNode.value) {
						return;
					}
					if (!e.dataTransfer.types.includes('custom')) {
						return;
					}

					//阻止默认不允许放置元素的行为,preventDefault 或者return false
					//可以防止将flip动画中pointerEvents=设为all时会触发enter事件,造成鼠标闪一下禁用状态;
					e.preventDefault();

					const parent = dragingNode.value.parentElement;

					const dragIndex = Array.from(parent.children).indexOf(dragingNode.value);
					const dropIndex = Array.from(parent.children).indexOf(dropNode);

					if (dragIndex > dropIndex) {
						//如果拖拽元素在被放入元素的前面,则将被放入元素插入到拖拽元素的前面
						dropNode.insertAdjacentElement('beforebegin', dragingNode.value);
					} else {
						//如果拖拽元素在被放入元素的后面,则将被放入元素插入到拖拽元素的后面
						dropNode.insertAdjacentElement('afterend', dragingNode.value);

					}
					flipDh.play();
				};
				const ondragover = (e) => {
					//判断是否为允许进入的元素
					if (e.dataTransfer.types.includes('custom')) {
						//阻止默认不允许放置元素的行为,preventDefault 或者return false
						e.preventDefault();
						//允许被那些操作类型的拖拽元素放置,对应拖拽元素e.dataTransfer.effectAllowed属性
						// 如果没有设置 dropEffect,浏览器会根据拖拽的元素类型和 effectAllowed 的值来决定
						//用于ondrop事件是否执行和鼠标效果: copy|link|move|none
						e.dataTransfer.dropEffect = 'move';
					}
				};
				const ondragend = (e) => {
					console.log('拖拽结束', e);
					dragingNode.value = null;
				};
				const ondrop = (e) => {
					console.log('放置元素', e);
				};
				return {
					dragingNode, ondragstart, ondragenter, ondragover, ondragend,ondrop
				};
			}
		}).mount('.drag-list');

		let dragContainer = document.querySelector('.container');
		//进入容器事件,在容器内就持续执行
		dragContainer.ondragover = function (e) {
			//阻止默认不允许放置元素的行为
			e.preventDefault();
			e.dataTransfer.dropEffect = 'copy';
		};
		dragContainer.ondrop = (e) => {
			//阻止默认事件,如拖入文件自动开启下载
			e.preventDefault();
			//drop事件中可以获取dataTransfer中的数据,类型,文件
			console.log('有效放置', e);
			console.log(e.dataTransfer.getData('custom'));
			console.log(e.dataTransfer.getData('text/plain'));
			console.log(e.dataTransfer.types);
			console.log(e.dataTransfer.files);
		};

		//FLIP动画;
		function FlipAnimation(animationElements = []) {
			// 初始化动画元素列表
			this.animationElements = [...animationElements];

			// 保存每个元素的初始位置
			for (let i of this.animationElements) {
				i.x = i.offsetLeft;
				i.y = i.offsetTop;
				i.style.willChange = 'transform';
			}
		}
		// 定义播放动画的方法
		FlipAnimation.prototype.play = function (animationTime = 200) {
			// 遍历每个动画元素
			for (let i of this.animationElements) {
				// 计算元素的新位置与旧位置的差值
				i.diffX = i.x - i.offsetLeft;
				i.diffY = i.y - i.offsetTop;

				// 更新元素的当前位置
				i.x = i.offsetLeft;
				i.y = i.offsetTop;

				// 禁用元素的指针事件，以防止在动画期间触发鼠标事件
				i.style.pointerEvents = 'none';
				// 在动画结束后恢复指针事件
				setTimeout(() => {
					i.style.pointerEvents = 'all';
				}, animationTime);
			}
			// 设置元素的初始变换状态为新的偏移量，移除过渡效果
			this.animationElements.forEach((i) => {
				i.style.transform = `translate(${i.diffX}px, ${i.diffY}px)`;
				i.style.transition = '0s';
			});
			// 使用双重 requestAnimationFrame 确保浏览器完成当前的重绘
			requestAnimationFrame(() => {
				requestAnimationFrame(() => {
					// 设置动画的过渡效果，并将元素变换回原始位置
					this.animationElements.forEach((i) => {
						i.style.transition = animationTime / 1000 + 's';
						i.style.transform = `translate(0px, 0px)`;
					});
				});
			});
		};
	</script>
</body>

</html>